Skip to main content

Axir Wallet
Social Recovery Contract

The AxirSocialRecovery contract provides a mechanism for social recovery of wallet ownership. Below is an explanation of its important functions.

Contract Structure

State Variables

  • guardians: Maps a wallet address to its array of guardian addresses.
  • thresholds: Maps a wallet address to its guardian threshold.
  • recoveries: Maps a wallet address to its current recovery struct.
  • nonces: Maps a wallet address to its current nonce for replay protection.
  • RECOVERY_DELAY: A constant specifying the delay time for recovery execution (3 days).

Events

  • RecoveryInitiated: Emitted when a recovery process is initiated.
  • RecoveryExecuted: Emitted when a recovery process is successfully executed.
  • RecoveryCancelled: Emitted when a recovery process is cancelled.

Functions

onInstall(bytes calldata data)

Installs the module in a wallet. This function decodes the provided data to initialize guardians and the threshold for a wallet.

function onInstall(bytes calldata data) external override {
if (isInitialized(msg.sender)) {
revert AlreadyInitialized(msg.sender);
}

(uint256 _threshold, address[] memory _guardians) = abi.decode(
data,
(uint256, address[])
);
require(
_threshold > 0 && _threshold <= _guardians.length,
"Invalid threshold"
);

guardians[msg.sender] = _guardians;
thresholds[msg.sender] = _threshold;
}

onUninstall(bytes calldata)

Uninstalls the module from a wallet. This function deletes all associated data for the wallet.

function onUninstall(bytes calldata) external override {
if (!isInitialized(msg.sender)) {
revert NotInitialized(msg.sender);
}
delete guardians[msg.sender];
delete thresholds[msg.sender];
delete recoveries[msg.sender];
delete nonces[msg.sender];
}

isModuleType(uint256 moduleTypeId)

Checks if the module type matches the executor type.

function isModuleType(uint256 moduleTypeId) external pure override returns (bool) {
return moduleTypeId == MODULE_TYPE_EXECUTOR;
}

isInitialized(address smartAccount)

Checks if the module is initialized for a wallet.

function isInitialized(address smartAccount) public view override returns (bool) {
return guardians[smartAccount].length > 0;
}

getHash(address _wallet, address _newOwner)

Generates a hash for the recovery process based on the wallet, new owner, and nonce.

function getHash(address _wallet, address _newOwner) public view returns (bytes32) {
return keccak256(abi.encodePacked(_wallet, _newOwner, nonces[_wallet]));
}

initiateRecovery(address _wallet, address _newOwner, bytes[] memory _signatures)

Initiates a recovery process. Verifies the signatures of guardians and sets the recovery details.

function initiateRecovery(
address _wallet,
address _newOwner,
bytes[] memory _signatures
) external {
require(
recoveries[_wallet].executionTime == 0,
"Recovery already in progress"
);

bytes32 recoveryHash = getHash(_wallet, _newOwner);
uint256 validSignatures = 0;

for (uint i = 0; i < _signatures.length; i++) {
address signer = recoveryHash.toEthSignedMessageHash().recover(
_signatures[i]
);
if (isGuardian(_wallet, signer)) {
validSignatures++;
}
}

require(
validSignatures >= thresholds[_wallet],
"Not enough valid signatures"
);

recoveries[_wallet] = Recovery({
newOwner: _newOwner,
guardianThreshold: validSignatures,
executionTime: block.timestamp + RECOVERY_DELAY,
executed: false
});

nonces[_wallet]++;

emit RecoveryInitiated(
_wallet,
_newOwner,
recoveries[_wallet].executionTime
);
}

executeRecovery(address _wallet)

Executes a recovery process after the recovery delay has passed.

function executeRecovery(address _wallet) external {
Recovery storage recovery = recoveries[_wallet];
require(recovery.executionTime > 0, "No recovery in progress");
require(
block.timestamp >= recovery.executionTime,
"Recovery delay not passed"
);
require(!recovery.executed, "Recovery already executed");

recovery.executed = true;
OwnerManager(_wallet).resetOwners(recovery.newOwner);

delete recoveries[_wallet];

emit RecoveryExecuted(_wallet, recovery.newOwner);
}

cancelRecovery()

Cancels a recovery process.

function cancelRecovery() external {
require(
recoveries[msg.sender].executionTime > 0,
"No recovery in progress"
);
delete recoveries[msg.sender];
emit RecoveryCancelled(msg.sender);
}

isGuardian(address _wallet, address _guardian)

Checks if an address is a guardian for a wallet.

function isGuardian(address _wallet, address _guardian) public view returns (bool) {
address[] storage walletGuardians = guardians[_wallet];
for (uint i = 0; i < walletGuardians.length; i++) {
if (walletGuardians[i] == _guardian) {
return true;
}
}
return false;
}

Summary

The AxirSocialRecovery contract enables secure recovery of wallet ownership through a consensus of guardians. It provides mechanisms to install and uninstall the module, initiate and execute recoveries, and validate guardianship.